//
//  ZYImageDownloadQueue.m
//  ZYWebImage
//
//  Created by wangzhipan on 2025/3/6.
//

#import "ZYImageDownloader.h"
#import "ZYImageOperation.h"
#import "ZYImageOperationToken.h"
#import "ZYDownloadTask.h"



@interface ZYImageDownloader() {
    NSLock *_urlOperationsLock;
}
@property (nonatomic, strong) NSOperationQueue *downloadQueue;
@property (nonatomic, strong) NSMutableDictionary <NSString *, ZYImageOperation *>*urlOperations;
@property (nonatomic, strong) NSURLSession *session;
@end

@implementation ZYImageDownloader
+ (instancetype)sharedInstance
{
    static id _instance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _instance = [[self alloc] init];
    });
    return _instance;
}

- (instancetype)init
{
    if (self = [super init]) {
        self.downloadQueue = [[NSOperationQueue alloc] init];
        //最大并发数默认6个
        self.downloadQueue.maxConcurrentOperationCount = 6;
        self.downloadQueue.name = @"com.imageDownloader.www";
        self.urlOperations = [NSMutableDictionary dictionary];
        self.session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
        _urlOperationsLock = [NSLock new];
    }
    return self;
}

- (nullable ZYDownloadTask *)downloadImageWithUrlStr:(nullable NSString *)urlStr
                                           completed:(nullable ZYDownloadCompleted)completed
{
    if (!urlStr.length) {
        completed ? completed(nil, nil, [NSError errorWithDomain:@"Image url is nil" code:-1 userInfo:nil]) : nil;
        return nil;
    }
    //先看能不能复用Operation
    BOOL notReuseOperation = NO;
    ZYImageOperation *operation = nil;
    [_urlOperationsLock lock];
    operation = self.urlOperations[urlStr];
    [_urlOperationsLock unlock];
    
    if (operation) {
        @synchronized (operation) {
            notReuseOperation = operation.isCancelled || operation.isFinished;
        }
    }
    else {
        notReuseOperation = YES;
    }
    
    ZYImageOperationToken *token = nil;
    //不能复用，就重新创建并下载，要注意线程安全问题
    if (notReuseOperation) {
        operation = [self createOperationWithUrlStr:urlStr];
        [_urlOperationsLock lock];
        self.urlOperations[urlStr] = operation;
        [_urlOperationsLock unlock];
        
        @weakify(self)
        operation.completionBlock = ^{
            @strongify(self)
            if (!self) {
                return;
            }
            [self->_urlOperationsLock lock];
            [self.urlOperations removeObjectForKey:urlStr];
            [self->_urlOperationsLock unlock];
        };
        /*
         注意，需要加锁：
         - operations 属性的访问是线程安全的
         - addOperation 方法本身是线程安全的
         - addDependency: 方法本身是线程安全的
         - 但是在遍历 operations 的同时，如果另一个线程修改了队列（添加或删除操作），会导致问题
         */
        [_urlOperationsLock lock];
        [self.downloadQueue addOperation:operation];
        [_urlOperationsLock unlock];
        token = [operation addCompletedBlock:completed];
    }
    else {
        //拿原来的operation，把completed 添加进去
        @synchronized (operation) {
            token = [operation addCompletedBlock:completed];
        }
    }
    return [[ZYDownloadTask alloc] initWithOperation:operation token:token];
}

- (void)cancelAllDownloads
{
    [self.downloadQueue cancelAllOperations];
}

#pragma mark - private
- (ZYImageOperation *)createOperationWithUrlStr:(NSString *)urlStr
{
    
    ZYImageOperation *result = [[ZYImageOperation alloc] initWithUrl:urlStr session:self.session];
    /*设置后加先下载的任务依赖关系，可以让在滚动视图图片下载时，后加入的任务优先下载
     【一定程度上可以滑动时优先下载当前屏幕上的任务】
     
     当然sd也提供了先进先下载的模式设置
     
     注意，需要加锁：
     - operations 属性的访问是线程安全的
     - addOperation 方法本身是线程安全的
     - addDependency: 方法本身是线程安全的
     - 但是在遍历 operations 的同时，如果另一个线程修改了队列（添加或删除操作），可能会导致问题
     */
    
    [_urlOperationsLock lock];
    for (NSOperation *pendingOperation in self.downloadQueue.operations) {
        [pendingOperation addDependency:result];
    }
    [_urlOperationsLock unlock];
    return result;
}




#pragma mark - getter && setter
- (BOOL)isSuspended
{
    return self.downloadQueue.isSuspended;
}

- (void)setSuspended:(BOOL)suspended
{
    self.downloadQueue.suspended = suspended;
}

- (NSUInteger)currentDownloadCount
{
    return self.downloadQueue.operationCount;
}
@end
